<template>
  <div ref="containerRef" :class="[ 'galaxy-container', state.theme ]">
    <svg ref="svgRef" :width="state.width" :height="state.height" class="galaxy-force-tree" :class="[state.theme]"></svg>
    <div class="toolbar">
      <button @click="back">Back</button>
      <button @click="resetGraph">重置图表</button>
      <div class="zoom-controls">
        <button @click="zoomIn">放大</button>
        <button @click="zoomOut">缩小</button>
      </div>
      <button @click="moveToViewCenter">视图中心</button>
      <button @click="toggleTheme">切换主题{{ state.theme }}</button>
    </div>
    
    <div 
      ref="contextMenuRef" 
      class="context-menu" 
      :class="[state.theme]"
      :style="{ left: menuState.x + 'px', top: menuState.y + 'px', display: menuState.show ? 'block' : 'none' }"
    >
      <ul v-if="menuState.targetNode">
        <li @click="handleMenuAction('explode')">衍生节点</li>
        <li @click="handleMenuAction('focus')">聚焦节点</li>
        <li @click="handleMenuAction('center')">移动到中心</li>
        <li @click="handleMenuAction('delete')" :class="{ disabled: menuState.targetNode?.id === '0' }">
          删除节点
        </li>
      </ul>
      <ul v-else>
        <li @click="moveToViewCenter">回到视图中心</li>
        <li @click="zoomIn">放大</li>
        <li @click="zoomOut">缩小</li>
        <li @click="resetGraph">重置</li>
        <li @click="resetGraph">随机选中一个节点</li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, onUnmounted, nextTick, watch } from 'vue'
import * as d3 from 'd3'
import aiw from '@/aiw-agent';
import { useRouter } from 'vue-router';

const router = useRouter()
const back = () => router.back();

// 常量配置
const CONFIG = {
  NODE_RADIUS: 20,
  LINK_DISTANCE: 150,      // 连接线理想距离（增大可减少拥挤）
  CHARGE_STRENGTH: -300,    // 电荷力强度（绝对值减小，排斥力减弱）
  ZOOM_SCALE_EXTENT: [0.2, 4],
  TRANSITION_DURATION: 600,
  ALPHA_MIN: 0.01,         // 最小动能阈值（增大可提前停止运动）
  ALPHA_DECAY: 0.1,       // 动能衰减率（增大可加速稳定）
  NODE_LOCK_DURATION: 1000,
  EDGE_BUFFER: 100 // 边缘缓冲区大小
}

// 状态管理
const state = reactive({
  width: window.innerWidth,
  height: window.innerHeight,
  focusedNode: null,
  theme: 'light'
})
const containerRef = ref()
const svgRef = ref()
const contextMenuRef = ref()

// 右键菜单状态
const menuState = reactive({
  show: false,
  x: 0,
  y: 0,
  targetNode: null
})

let clusterCounter = 0
let colorGroupCounter = 0

const colorScale = d3.scaleOrdinal(d3.schemeTableau10)

const root = ref({
  id: '0',
  label: '奇点',
  exploded: false,
  loading: false,
  cluster: clusterCounter++,
  colorGroup: colorGroupCounter++,
  children: [],
  x: state.width / 2,
  y: state.height / 2
})

// D3相关变量
let simulation, zoom, transformState
let nodeEls, linkEls
let gContent, gLinks, gNodes

// 右键菜单处理函数
function showContextMenu(event, node) {
  event.preventDefault() // 阻止默认右键菜单
  
  // 设置菜单位置和目标节点
  menuState.show = true
  menuState.x = event.clientX
  menuState.y = event.clientY
  menuState.targetNode = node
  
  // 确保菜单不超出视口
  const menuEl = contextMenuRef.value
  if (menuEl) {
    const rect = menuEl.getBoundingClientRect()
    if (menuState.x + rect.width > window.innerWidth) {
      menuState.x = window.innerWidth - rect.width - 10
    }
    if (menuState.y + rect.height > window.innerHeight) {
      menuState.y = window.innerHeight - rect.height - 10
    }
  }
}

function hideContextMenu() {
  menuState.show = false
  menuState.targetNode = null
}

function handleMenuAction(action) {
  const node = menuState.targetNode
  if (!node) return
  
  switch (action) {
    case 'explode':
      // 展开节点（生成子节点）
      assignNewColorGroup(node)
      mockExplosion(node).then(() => update())
      break
    case 'focus':
      // 聚焦节点
      state.focusedNode = node
      highlightFocusedNode()
      moveNodeToViewCenter(node)
      break
    case 'center':
      // 移动节点到视图中心
      moveNodeToViewCenter(node)
      break
    case 'delete':
      // 删除节点（根节点不能删除）
      if (node.id !== '0') {
        deleteNode(node)
      }
      break
  }
  
  hideContextMenu()
}

// 删除节点及其子节点
function deleteNode(nodeToDelete) {
  // 递归查找并删除节点
  function removeNode(parent, targetId) {
    if (!parent || !parent.children) return false
    
    for (let i = 0; i < parent.children.length; i++) {
      if (parent.children[i].id === targetId) {
        parent.children.splice(i, 1)
        return true
      }
      
      if (removeNode(parent.children[i], targetId)) {
        return true
      }
    }
    
    return false
  }
  
  // 从根节点开始查找并删除
  removeNode(root.value, nodeToDelete.id)
  
  // 如果删除的是当前聚焦节点，则取消聚焦
  if (state.focusedNode && state.focusedNode.id === nodeToDelete.id) {
    state.focusedNode = null
  }
  
  // 更新图表
  update()
}

// 初始化图表
function initializeSimulation(nodes, links, centerNode = null) {
  simulation = d3.forceSimulation(nodes)
    .force('link', d3.forceLink(links).id(d => d.id).distance(CONFIG.LINK_DISTANCE).strength(1)) // 增强连接力
    .force('charge', d3.forceManyBody().strength(CONFIG.CHARGE_STRENGTH))
    .force('center', centerNode 
      ? d3.forceCenter(centerNode.x, centerNode.y).strength(0.1) 
      : d3.forceCenter(state.width / 2, state.height / 2)
    )
    .force('collide', d3.forceCollide(CONFIG.NODE_RADIUS * 1.5)) // 节点碰撞检测
    .alphaMin(CONFIG.ALPHA_MIN)
    .alphaDecay(CONFIG.ALPHA_DECAY)
    .on('tick', ticked);
}

function ticked() {
  if (linkEls) linkEls
    .attr('x1', d => d.source.x)
    .attr('y1', d => d.source.y)
    .attr('x2', d => d.target.x)
    .attr('y2', d => d.target.y)

  if (nodeEls) nodeEls.attr('transform', d => `translate(${d.x},${d.y})`)
}

function enableDrag(selection) {
  selection.call(
    d3.drag()
      .on('start', (event, d) => {
        if (!event.active) simulation.alphaTarget(0.3).restart()
        d.fx = d.x
        d.fy = d.y
      })
      .on('drag', (event, d) => {
        d.fx = event.x
        d.fy = event.y
      })
      .on('end', (event, d) => {
        if (!event.active) simulation.alphaTarget(0)
        d.fx = null
        d.fy = null
      })
  )
}

// 数据处理
function traverse(node, callback) {
  callback(node)
  if (node.children) {
    node.children.forEach(child => traverse(child, callback))
  }
}

function flattenTree(rootNode) {
  const nodes = []
  const links = []

  traverse(rootNode, node => {
    nodes.push(node)
    if (node.children) {
      node.children.forEach(child => {
        links.push({ source: node.id, target: child.id })
      })
    }
  })

  return { nodes, links }
}

// 高亮聚焦节点
function highlightFocusedNode() {
  if (!nodeEls || !state.focusedNode) return

  nodeEls.select('circle')
    .style('stroke-width', d =>
      d.id === state.focusedNode.id ? (d.loading ? 3 : 1.5) : 1.5
    )
    .style('stroke', d =>
      d.id === state.focusedNode.id ? '#fff' : (state.theme === 'light' ? '#333' : '#fff')
    )
}

function mockExplosion(parent) {
  return new Promise(resolve => {
    if (!parent) {
      console.warn('警告：点击的节点不存在');
      return resolve();
    }

    state.focusedNode = parent;
    parent.loading = true;
    update();

    parent.fx = parent.x;
    parent.fy = parent.y;

    if (parent.colorGroup === undefined) {
      parent.colorGroup = colorGroupCounter++;
    }
    let prompt = '';
    if (parent.index > 0 && parent?.label) {
      prompt = `请基于"${parent.label}"，给我衍生出10个词汇，`;
    } else {
      prompt = '请给我随机10个词汇，';
    }
    prompt += '使用中文逗号“，”分割，词汇使用中文。不要多余的话，不要序号，不要空格，只给我词汇即可，格式如：“词汇1，词汇2，词汇3”';
    aiw.bridge.send('agent.generate', {
      prompt: prompt,
      stream: false
    });
    aiw.bridge.once('agent.generate', ({ status, data, message }, isStream, id, source) => {
      if (status === 'SUCCEEDED') {
        const list = data.split('，');
        const newNodes = list.map((item, i) => ({
          id: `${parent.id}-${Date.now()}-${i}`,
          label: item,
          exploded: false,
          loading: false,
          cluster: parent.cluster,
          colorGroup: parent.colorGroup,
          children: [],
          x: parent.x,
          y: parent.y,
          parentId: parent.id // 明确父节点 ID
        }));

        const radius = CONFIG.NODE_RADIUS * (parent.children.length + 1) * 8;
        newNodes.forEach((node, i) => {
          const angle = (i / list.length) * 2 * Math.PI;
          node.x = parent.x + radius * Math.cos(angle);
          node.y = parent.y + radius * Math.sin(angle);
        });

        if (!Array.isArray(parent.children)) {
          parent.children = [];
        }
        parent.children.push(...newNodes);

        parent.loading = false;

        setTimeout(() => {
          parent.fx = null;
          parent.fy = null;
          resolve();
        }, CONFIG.NODE_LOCK_DURATION);

        update();
      } else if (status === '[END]' || status === 'ERROR') {
        parent.loading = false;
      }
    })
  })
}

// 当节点爆炸时，为其分配新的颜色组
function assignNewColorGroup(node) {
  node.colorGroup = colorGroupCounter++

  if (node.children) {
    node.children.forEach(child => {
      child.colorGroup = node.colorGroup
      assignNewColorGroup(child)
    })
  }
}

// 更新图表
function update() {
  if (!svgRef.value) return

  const svg = d3.select(svgRef.value)
  const { nodes, links } = flattenTree(root.value)

  if (!gContent) {
    gContent = svg.append('g').attr('class', 'content')
    gLinks = gContent.append('g').attr('class', 'links')
    gNodes = gContent.append('g').attr('class', 'nodes')

    if (zoom && transformState) {
      svg.call(zoom).call(zoom.transform, transformState)
    } else {
      zoom = d3.zoom()
        .scaleExtent(CONFIG.ZOOM_SCALE_EXTENT)
        .on('zoom', zoomed)

      svg.call(zoom)
    }
  }

  if (!simulation) {
    initializeSimulation(nodes, links)
  } else {
    simulation.nodes(nodes)
    simulation.force('link').links(links)
    simulation.force('charge').strength(-300)
    simulation.alpha(1).restart()
  }

  // 更新连接线
  linkEls = gLinks.selectAll('line')
    .data(links, d => `${d.source.id}-${d.target.id}`)
    .join(
      enter => enter.append('line')
        .attr('stroke', d => colorScale(d.source.colorGroup))
        .attr('stroke-width', 1)
        .attr('opacity', 0)
        .transition()
        .duration(CONFIG.TRANSITION_DURATION)
        .attr('opacity', 0.6),
      update => update
        .attr('stroke', d => colorScale(d.source.colorGroup)),
      exit => exit.transition()
        .duration(CONFIG.TRANSITION_DURATION)
        .attr('opacity', 0)
        .remove()
    )

  // 更新节点
  nodeEls = gNodes.selectAll('g.node')
    .data(nodes, d => d.id)
    .join(
      enter => {
        const nodeGroup = enter.append('g')
          .attr('class', 'node')
          .attr('transform', d => `translate(${d.x ?? state.width / 2},${d.y ?? state.height / 2})`)
          .attr('opacity', 0)

        // 节点圆形
        const circle = nodeGroup.append('circle')
          .attr('r', 0)
          .attr('fill', d => colorScale(d.colorGroup))
          .attr('stroke', state.theme === 'light' ? '#333' : '#fff')
          .attr('stroke-width', 1.5)
          .style('cursor', 'pointer')
          .on('click', async (e, d) => {
            if (d.loading) return
            assignNewColorGroup(d)
            await mockExplosion(d)
            update()
          })
          .on('contextmenu', (e, d) => {
            // 右键点击事件 - 显示自定义菜单
            showContextMenu(e, d)
          })
          .on('mouseover', (e, d) => {
            d3.select(e.currentTarget)
              .transition()
              .duration(200)
              .attr('r', CONFIG.NODE_RADIUS * 1.2)
              .attr('stroke-width', d.id === state.focusedNode?.id ? 3 : 2)
          })
          .on('mouseout', (e, d) => {
            d3.select(e.currentTarget)
              .transition()
              .duration(200)
              .attr('r', CONFIG.NODE_RADIUS)
              .attr('stroke-width', d.id === state.focusedNode?.id ? 3 : 1.5)
          })

        // 节点标签
        nodeGroup.append('text')
          .text(d => d.label)
          .attr('x', 24)
          .attr('y', 4)
          .attr('fill', state.theme === 'light' ? '#333' : '#fff')
          .attr('font-size', 12)
          .attr('pointer-events', 'none')
          .style('user-select', 'none')
          .attr('opacity', 0)

        // 增强版加载指示器
        const loadingGroup = nodeGroup.append('g')
          .attr('class', 'loading-indicator')
          .style('display', 'none')

        // 脉冲环
        const pulseRing = loadingGroup.append('circle')
          .attr('r', CONFIG.NODE_RADIUS)
          .attr('fill', 'none')
          .attr('stroke', '#fff')
          .attr('stroke-width', 2)

        // 旋转环
        const spinRing = loadingGroup.append('circle')
          .attr('r', CONFIG.NODE_RADIUS - 4)
          .attr('fill', 'none')
          .attr('stroke', '#fff')
          .attr('stroke-width', 2)
          .attr('stroke-dasharray', '25, 10')

        // 中心圆点
        const centerDot = loadingGroup.append('circle')
          .attr('r', 3)
          .attr('fill', '#fff')

        // 定义动画函数
        function animatePulse() {
          pulseRing.transition()
            .duration(2000)
            .attr('r', CONFIG.NODE_RADIUS * 2)
            .attr('opacity', 0)
            .on('end', () => {
              pulseRing.attr('r', CONFIG.NODE_RADIUS).attr('opacity', 1)
              animatePulse()
            })
        }

        function animateSpin() {
          spinRing.transition()
            .duration(2000)
            .attrTween('transform', () => d3.interpolateString('rotate(0)', 'rotate(360)'))
            .on('end', animateSpin)
        }

        function animateDot() {
          centerDot.transition()
            .duration(1500)
            .attr('r', 5)
            .attr('opacity', 0.7)
            .transition()
            .duration(1500)
            .attr('r', 3)
            .attr('opacity', 1)
            .on('end', animateDot)
        }

        // 进入动画
        nodeGroup.transition()
          .duration(CONFIG.TRANSITION_DURATION)
          .attr('opacity', 1)

        circle.transition()
          .duration(CONFIG.TRANSITION_DURATION)
          .attr('r', CONFIG.NODE_RADIUS)
          .attr('fill', d => colorScale(d.colorGroup))

        nodeGroup.select('text')
          .transition()
          .duration(CONFIG.TRANSITION_DURATION)
          .attr('opacity', 1)

        return nodeGroup
      },
      update => update,
      exit => exit.transition()
        .duration(CONFIG.TRANSITION_DURATION)
        .attr('opacity', 0)
        .remove()
    )

  // 更新加载状态
  const loadingIndicators = nodeEls.select('.loading-indicator')
    .style('display', d => d.loading ? 'block' : 'none')

  // 为可见的加载指示器启动动画
  loadingIndicators.each(function () {
    const group = d3.select(this)

    // 只有当元素可见时才启动动画
    if (group.style('display') !== 'none') {
      const pulseRing = group.select('circle:nth-child(1)')
      const spinRing = group.select('circle:nth-child(2)')
      const centerDot = group.select('circle:nth-child(3)')

      // 确保动画只启动一次
      if (!pulseRing.attr('data-animating')) {
        pulseRing.attr('data-animating', 'true')

        // 脉冲动画
        function animatePulse() {
          pulseRing.transition()
            .duration(2000)
            .attr('r', CONFIG.NODE_RADIUS * 2)
            .attr('opacity', 0)
            .on('end', () => {
              pulseRing.attr('r', CONFIG.NODE_RADIUS).attr('opacity', 1)
              animatePulse()
            })
        }

        // 旋转动画
        function animateSpin() {
          spinRing.transition()
            .duration(2000)
            .attrTween('transform', () => d3.interpolateString('rotate(0)', 'rotate(360)'))
            .on('end', animateSpin)
        }

        // 圆点脉动动画
        function animateDot() {
          centerDot.transition()
            .duration(1500)
            .attr('r', 5)
            .attr('opacity', 0.7)
            .transition()
            .duration(1500)
            .attr('r', 3)
            .attr('opacity', 1)
            .on('end', animateDot)
        }

        animatePulse()
        animateSpin()
        animateDot()
      }
    }
  })

  // 高亮聚焦的节点
  highlightFocusedNode()

  // 启用拖拽
  enableDrag(nodeEls)
}

// 缩放处理
function zoomed({ transform }) {
  transformState = transform || d3.zoomIdentity
  if (gContent) {
    gContent.attr('transform', transformState)
  }
}

function zoomIn() {
  if (!svgRef.value || !zoom) return
  d3.select(svgRef.value).transition()
    .duration(500)
    .call(zoom.scaleBy, 1.2)
}

function zoomOut() {
  if (!svgRef.value || !zoom) return
  d3.select(svgRef.value).transition()
    .duration(500)
    .call(zoom.scaleBy, 0.8)
}

// 移动到视图中心
function moveToViewCenter() {
  if (!svgRef.value || !root.value || !transformState) return;

  const svg = d3.select(svgRef.value);
  
  // 窗口中心坐标
  const targetX = state.width / 2;
  const targetY = state.height / 2;
  
  // 当前变换状态
  const currentTransform = transformState;
  
  // 计算根节点在视图中的当前位置
  const viewRootX = root.value.x * currentTransform.k + currentTransform.x;
  const viewRootY = root.value.y * currentTransform.k + currentTransform.y;
  
  // 计算需要的平移量
  const dx = targetX - viewRootX;
  const dy = targetY - viewRootY;
  
  // 创建新的变换
  const targetTransform = currentTransform.translate(dx, dy);
  
  // 应用平滑过渡动画
  svg.transition()
    .duration(500)
    .call(zoom.transform, targetTransform);
}

// 检查节点位置并在必要时滚动画布
function checkAndScrollToNode(node) {
  if (!svgRef.value || !transformState) return;
  
  // 获取当前视图信息
  const currentTransform = transformState;
  const viewWidth = state.width;
  const viewHeight = state.height;
  
  // 计算节点在视图中的位置（考虑缩放和平移）
  const viewNodeX = node.x * currentTransform.k + currentTransform.x;
  const viewNodeY = node.y * currentTransform.k + currentTransform.y;
  
  // 计算节点在视图中的实际大小（考虑缩放）
  const viewNodeRadius = CONFIG.NODE_RADIUS * currentTransform.k;
  
  // 计算安全区域边界（考虑节点大小）
  const safeLeft = viewNodeRadius;
  const safeRight = viewWidth - viewNodeRadius;
  const safeTop = viewNodeRadius;
  const safeBottom = viewHeight - viewNodeRadius;
  
  // 检查节点是否完全在安全区域内
  const isInSafeArea = 
    viewNodeX >= safeLeft && 
    viewNodeX <= safeRight && 
    viewNodeY >= safeTop && 
    viewNodeY <= safeBottom;
  
  // 如果节点不在安全区域内，则将其移动到中心
  if (!isInSafeArea) {
    moveNodeToViewCenter(node);
  }
}

// 将指定节点移动到视图中心
function moveNodeToViewCenter(node) {
  if (!svgRef.value || !transformState) return;

  const svg = d3.select(svgRef.value);
  const currentTransform = transformState;
  const viewWidth = state.width;
  const viewHeight = state.height;

  // 计算目标节点在视图中的理想位置（中心）
  const targetViewX = viewWidth / 2;
  const targetViewY = viewHeight / 2;

  // 计算目标节点在当前变换下的实际视图坐标
  const currentViewX = node.x * currentTransform.k + currentTransform.x;
  const currentViewY = node.y * currentTransform.k + currentTransform.y;

  // 计算平移量（使目标节点移动到视图中心）
  const dx = targetViewX - currentViewX;
  const dy = targetViewY - currentViewY;

  // 应用平滑过渡
  svg.transition()
    .duration(500)
    .call(zoom.transform, currentTransform.translate(dx, dy));
}

// 重置图表
function resetGraph() {
  state.focusedNode = null
  clusterCounter = 0
  colorGroupCounter = 0
  root.value = {
    id: '0',
    label: '奇点',
    exploded: false,
    loading: false,
    cluster: clusterCounter++,
    colorGroup: colorGroupCounter++,
    children: [],
    x: state.width / 2,
    y: state.height / 2
  }
  update()
}

const toggleTheme = () => {
  state.theme = state.theme === 'dark' ? 'light' : 'dark';
  aiw.toggleTheme(state.theme);
  
  // 延迟更新，确保DOM更新完成
  nextTick(() => {
    const svg = d3.select(svgRef.value);
    
    // 更新节点样式
    const nodes = svg.selectAll('.node circle');
    const labels = svg.selectAll('.node text');
    const links = svg.selectAll('.links line');
    
    if (state.theme === 'light') {
      // 浅色主题样式
      nodes
        .attr('fill', d => colorScale(d.colorGroup))
        .attr('stroke', '#333')
      
      labels.attr('fill', '#333');
      links.attr('stroke', d => colorScale(d.source.colorGroup)?.darker(2));
    } else {
      // 深色主题样式
      nodes
        .attr('fill', d => colorScale(d.colorGroup))
        .attr('stroke', '#fff')
      
      labels.attr('fill', '#fff');
      links.attr('stroke', d => colorScale(d.source.colorGroup));
    }
    
    // 添加平滑过渡
    nodes.transition().duration(300).attr('stroke', state.theme === 'light' ? '#333' : '#fff');
    labels.transition().duration(300).attr('fill', state.theme === 'light' ? '#333' : '#fff');
    links.transition().duration(300).attr('stroke', d => 
      state.theme === 'light' ? colorScale(d.source.colorGroup)?.darker(2) : colorScale(d.source.colorGroup)
    );
  });
};

// 响应式处理
const resizeObserver = new ResizeObserver(() => {
  if (containerRef.value) {
    state.width = containerRef.value.clientWidth
    state.height = containerRef.value.clientHeight
    root.value.x = state.width / 2
    root.value.y = state.height / 2
    update()
  }
})

// 生命周期钩子
onMounted(() => {
  if (containerRef.value) {
    state.width = containerRef.value.clientWidth
    state.height = containerRef.value.clientHeight
    root.value.x = state.width / 2
    root.value.y = state.height / 2
  }

  zoom = d3.zoom()
    .scaleExtent(CONFIG.ZOOM_SCALE_EXTENT)
    .on('zoom', zoomed)

  svgRef.value.addEventListener('contextmenu', (e) => {
    if (!e.target.closest('.node')) {
      if (menuState.show) {
        hideContextMenu();
      } else {
        showContextMenu(e, null);
      }
    }
  })

  // 点击文档其他地方关闭菜单
  document.addEventListener('click', hideContextMenu)

  resizeObserver.observe(containerRef.value)
  nextTick(update)
})

onUnmounted(() => {
  resizeObserver.disconnect()
  if (simulation) simulation.stop()
  if (svgRef.value) {
    d3.select(svgRef.value).on('.zoom', null)
  }
  document.removeEventListener('click', hideContextMenu)
})
</script>

<style lang="scss" scoped>
.galaxy-container {
  width: 100%;
  height: 100vh;
  position: relative;
  transition: background-color 0.3s ease;
}
// 暗黑主题（默认）
.galaxy-container.dark {
  background: radial-gradient(#111, #000);
  
  .toolbar button {
    background-color: rgba(255, 255, 255, 0.1);
    border-color: rgba(255, 255, 255, 0.3);
    color: white;
  }
}

// 明亮主题
.galaxy-container.light {
  background: radial-gradient(#f5f5f5, #ffffff);
  
  .toolbar button {
    background-color: rgba(0, 0, 0, 0.1);
    border-color: rgba(0, 0, 0, 0.3);
    color: #333;
  }
}

.galaxy-force-tree {
  cursor: grab;
}

.galaxy-force-tree:active {
  cursor: grabbing;
}

.toolbar {
  position: absolute;
  top: calc(var(--statusbar-height) + 16px);
  left: 10px;
  z-index: 10;
  display: flex;
  gap: 10px;
}

.zoom-controls {
  display: flex;
  gap: 5px;
}

button {
  padding: 6px 12px;
  border-radius: 4px;
  border: 1px solid;
  cursor: pointer;
  transition: all 0.2s ease;
}

button:hover {
  opacity: 0.8;
  transform: translateY(-1px);
}

// 右键菜单样式
.context-menu {
  position: absolute;
  z-index: 100;
  min-width: 150px;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  padding: 5px 0;
  margin: 0;
  list-style: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  transform-origin: top left;
  transform: scale(0.95);
  opacity: 0;
}

.context-menu[style*="display: block"] {
  transform: scale(1);
  opacity: 1;
}

.context-menu.dark {
  background-color: #2d2d2d;
  border: 1px solid #444;
}

.context-menu.light {
  background-color: #fff;
  border: 1px solid #ddd;
}

.context-menu ul {
  padding: 0;
  margin: 0;
  list-style: none;
}

.context-menu li {
  padding: 8px 16px;
  cursor: pointer;
  transition: background-color 0.2s ease;
  user-select: none;
}

.context-menu.dark li {
  color: #fff;
}

.context-menu.light li {
  color: #333;
}

.context-menu li:hover {
  background-color: rgba(0, 0, 0, 0.1);
}

.context-menu.dark li:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

.context-menu li.disabled {
  color: #999;
  cursor: not-allowed;
  background-color: transparent !important;
}
</style>